Grails is no more or less secure than Java Servlets. However, Java servlets (and hence Grails) are extremely secure and largely immune to common buffer overrun and malformed URL exploits due to the nature of the Java Virtual Machine underpinning the code.
Web security problems typically occur due to developer naivety or mistakes, and there is a little Grails can do to avoid common mistakes and make writing secure applications easier to write.
What Grails Automatically Does
Grails has a few built in safety mechanisms by default.
- All standard database access via GORM domain objects is automatically SQL escaped to prevent SQL injection attacks
- The default scaffolding templates HTML escape all data fields when displayed
- Grails link creating tags (link, form, createLink, createLinkTo and others) all use appropriate escaping mechanisms to prevent code injection
- Grails provides codecs to allow you to trivially escape data when rendered as HTML, JavaScript and URLs to prevent injection attacks here.
SQL injection
Hibernate, which is the technology underlying GORM domain classes, automatically escapes data when committing to database so this is not an issue. However it is still possible to write bad dynamic HQL code that uses unchecked request parameters. For example doing the following is vulnerable to HQL injection attacks:
def vulnerable = {
def books = Book.find("from Book as b where b.title ='" + params.title + "'")
}
Do
not do this. If you need to pass in parameters use named or positional parameters instead:
def safe = {
def books = Book.find("from Book as b where b.title =?", [params.title])
}
Phishing
This really a public relations issue in terms of avoiding hijacking of your branding and a declared communication policy with your customers. Customers need to know how to identify bonafide emails received.
XSS - cross-site scripting injection
It is important that your application verifies as much as possible that incoming requests were originated from your application and not from another site. Ticketing and page flow systems can help this and Grails' support for
Spring Web Flow includes security like this by default.
It is also important to ensure that all data values rendered into views are escaped correctly. For example when rendering to HTML or XHTML you must call
encodeAsHTML on every object to ensure that people cannot maliciously inject JavaScript or other HTML into data or tags viewed by others. Grails supplies several
Dynamic Encoding Methods for this purpose and if your output escaping format is not supported you can easily write your own codec.
You must also avoid the use of request parameters or data fields for determining the next URL to redirect the user to. If you use a
successURL
parameter for example to determine where to redirect a user to after a successful login, attackers can imitate your login procedure using your own site, and then redirect the user back to their own site once logged in, potentially allowing JS code to then exploit the logged-in account on the site.
HTML/URL injection
This is where bad data is supplied such that when it is later used to create a link in a page, clicking it will not cause the expected behaviour, and may redirect to another site or alter request parameters.
HTML/URL injection is easily handled with the
codecs supplied by Grails, and the tag libraries supplied by Grails all use
encodeAsURL where appropriate. If you create your own tags that generate URLs you will need to be mindful of doing this too.
Denial of service
Load balancers and other appliances are more likely to be useful here, but there are also issues relating to excessive queries for example where a link is created by an attacker to set the maximum value of a result set so that a query could exceed the memory limits of the server or slow the system down. The solution here is to always sanitize request parameters before passing them to dynamic finders or other GORM query methods:
def safeMax = Math.max(params.max?.toInteger(), 100) // never let more than 100 results be returned
return Book.list(max:safeMax)
Guessable IDs
Many applications use the last part of the URL as an "id" of some object to retrieve from GORM or elsewhere. Especially in the case of GORM these are easily guessable as they are typically sequential integers.
Therefore you must assert that the requesting user is allowed to view the object with the requested id before returning the response to the user.
Not doing this is "security through obscurity" which is inevitably breached, just like having a default password of "letmein" and so on.
You must assume that every unprotected URL is publicly accessible one way or another.
Grails supports the concept of dynamic encode/decode methods. A set of standard codecs are bundled with Grails. Grails also supports a simple mechanism for developers to contribute their own codecs that will be recognized at runtime.
Codec Classes
A Grails codec class is a class that may contain an encode closure, a decode closure or both. When a Grails application starts up the Grails framework will dynamically load codecs from the
grails-app/utils/
directory.
The framework will look under
grails-app/utils/
for class names that end with the convention
Codec
. For example one of the standard codecs that ship with Grails is
HTMLCodec
.
If a codec contains an
encode
property assigned a block of code Grails will create a dynamic
encode
method and add that method to the Object class with a name representing the codec that defined the encode closure. For example, the
HTMLCodec
class defines an
encode
block so Grails will attach that closure to the
Object
class with the name
encodeAsHTML
.
The
HTMLCodec
and
URLCodec
classes also define a
decode
block so Grails will attach those with the names
decodeHTML
and
decodeURL
. Dynamic codec methods may be invoked from anywhere in a Grails application. For example, consider a case where a report contains a property called 'description' and that description may contain special characters that need to be escaped to be presented in an HTML document. One way to deal with that in a GSP is to encode the description property using the dynamic encode method as shown below:
${report.description.encodeAsHTML()}
Decoding is performed using
value.decodeHTML()
syntax.
Standard Codecs
HTMLCodecThis codec perfoms HTML escaping and unescaping, so that values you provide can be rendered safely in an HTML page without creating any HTML tags or damaging the page layout. For example, given a value "Don't you know that 2 > 1?" you wouldn't be able to show this safely within an HTML page because the > will look like it closes a tag, which is especially bad if you render this data within an attribute, such as the value attribute of an input field.
Example of usage:
<input name="comment.message" value="${comment.message.encodeAsHTML()}"/>
Note that the HTML encoding does not re-encode apostrophe/single quote so you must use double quotes on attribute values to avoid text with apostrophes messing up your page.
URLCodecURL encoding is required when creating URLs in links or form actions, or any time data may be used to create a URL. It prevents illegal characters getting into the URL to change its meaning, for example a "Apple & Blackberry" is not going to work well as a parameter in a GET request as the ampersand will break the parsing of parameters.
Example of usage:
<a href="/mycontroller/find?searchKey=${lastSearch.encodeAsURL()}">Repeat last search</a>
Base64CodecPerforms Base64 encode/decode functions. Example of usage:
Your registration code is: ${user.registrationCode.encodeAsBase64()}
JavaScriptCodecWill escape Strings so they can be used as valid JavaSctipt strings. Example of usage:
Element.update('${elementId}', '${render(template: "/common/message").encodeAsJavaScript()}')
HexCodecWill encode byte arrays or lists of integers to lowercase hexadecimal strings, and can decode hexadecimal strings into byte arrays. Example of usage:
Selected colour: #${[255,127,255].encodeAsHex()}
MD5CodecWill use the MD5 algorithm to digest byte arrays or lists of integers, or the bytes of a string (in default system encoding), as a lowercase hexadecimal string. Example of usage:
Your API Key: ${user.uniqueID.encodeAsMD5()}
MD5BytesCodecWill use the MD5 algorithm to digest byte arrays or lists of integers, or the bytes of a string (in default system encoding), as a byte array. Example of usage:
byte[] passwordHash = params.password.encodeAsMD5Bytes()
SHA1CodecWill use the SHA1 algorithm to digest byte arrays or lists of integers, or the bytes of a string (in default system encoding), as a lowercase hexadecimal string. Example of usage:
Your API Key: ${user.uniqueID.encodeAsSHA1()}
SHA1BytesCodecWill use the SHA1 algorithm to digest byte arrays or lists of integers, or the bytes of a string (in default system encoding), as a byte array. Example of usage:
byte[] passwordHash = params.password.encodeAsSHA1Bytes()
SHA256CodecWill use the SHA256 algorithm to digest byte arrays or lists of integers, or the bytes of a string (in default system encoding), as a lowercase hexadecimal string. Example of usage:
Your API Key: ${user.uniqueID.encodeAsSHA256()}
SHA256BytesCodecWill use the SHA256 algorithm to digest byte arrays or lists of integers, or the bytes of a string (in default system encoding), as a byte array. Example of usage:
byte[] passwordHash = params.password.encodeAsSHA256Bytes()
Custom Codecs
Applications may define their own codecs and Grails will load them along with the standard codecs. A custom codec class must be defined in the
grails-app/utils/
directory and the class name must end with
Codec
. The codec may contain a
static
encode
block, a
static
decode
block or both. The block should expect a single argument which will be the object that the dynamic method was invoked on. For Example:
class PigLatinCodec {
static encode = { str ->
// convert the string to piglatin and return the result
}
}
With the above codec in place an application could do something like this:
${lastName.encodeAsPigLatin()}
Although there is no current default mechanism for authentication as it is possible to implement authentication in literally thousands of different ways. It is however, trivial to implement a simple authentication mechanism using either
interceptors or
filters.
Filters allow you to apply authentication across all controllers or across a URI space. For example you can create a new set of filters in a class called
grails-app/conf/SecurityFilters.groovy
:
class SecurityFilters {
def filters = {
loginCheck(controller:'*', action:'*') {
before = {
if(!session.user && actionName != "login") {
redirect(controller:"user",action:"login")
return false
}
} }
}
}
Here the
loginCheck
filter will intercept execution
before an action executed and if their is no user in the session and the action being executed is not the
login
action then redirect to the
login
action.
The
login
action itself is trivial too:
def login = {
if(request.get) render(view:"login")
else {
def u = User.findByLogin(params.login)
if(u) {
if(u.password == params.password) {
session.user = u
redirect(action:"home")
}
else {
render(view:"login", model:[message:"Password incorrect"])
}
}
else {
render(view:"login", model:[message:"User not found"])
}
}
}
If you need more advanced functionality beyond simple authentication such as authorization, roles etc. then you may want to consider using one of the available security plug-ins.
The Acegi Plug-in is built on the
Spring Acegi project which provides a flexible, extensible framework for building all sorts of authentication and authorization schemes.
The Acegi plug-in requires you to specify a mapping between URIs and roles and provides a default domain model to model people, authorities and request maps. See the
documentation on the wiki for more information.
JSecurity is another Java POJO oriented security framework that again provides a default domain model that models realms, users, roles and permissions. With JSecurity you have to extends a controller base called called
JsecAuthBase
in each controller you want secured and then provide an
accessControl
block to setup the roles. An example below:
class ExampleController extends JsecAuthBase {
static accessControl = {
// All actions require the 'Observer' role.
role(name: 'Observer') // The 'edit' action requires the 'Administrator' role.
role(name: 'Administrator', action: 'edit') // Alternatively, several actions can be specified.
role(name: 'Administrator', only: [ 'create', 'edit', 'save', 'update' ])
} …
}
For more information on the JSecurity plug-in refer to the
JSecurity Quick Start.